//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "elasys_server.h"
#include "wincetypes.h"
#include "stdio.h"
#define USER_ENVIRONMENT_SIZE       0x2000      // 8KB
#define USER_DEVPATH_SIZE           0x2000      // 8KB
#define MAX_ENVIRONMENT_ITEM        127

#define ROUND4(n)   (((n)+3)&~3)    // round up to multiple of 4 bytes

//
//  Memory structure of environment segment:
//
//  |<-- max item + 1 -->|<-- max item + 1 ->|<------ c_iMaxBufferSize --->|
//  +---------------+----+--------------+----+-----------+-----------+-----+
//  | array of wenv |NULL| array of env |NULL| env block | env block | ... |
//  +---------------+----+--------------+----+-----------+-----------+-----+
//  ^                    ^                   ^
//  |                    |__ _environ        |__ c_pBeginOfBuffer
//  |____ _wenviron
//

extern wchar_t **_wenviron;
extern char **_environ;

static int s_iEnvCount = 0;

extern byte * c_pBeginOfBuffer;
extern int    c_iMaxBufferSize;

//
//  Memory structure of ONE environment block:
//
//  |<-- 4-->|<---- BLOCK_LENGTH(index) (# of bytes) ------>|
//  +--------+-------------------------+--------------------+
//  | length |//// (Unicode string) ///|///// (string) /////|
//  +--------+-------------------------+--------------------+
//  ^        ^                         ^                    ^
//  |        |__ WENV_VALUE(index)     |__ ENV_VALUE(index) |__ BLOCK_END(index)
//  |____ BLOCK_BEGIN(index)
//
#define WENV_VALUE(index)   (*(_wenviron + (index)))
#define ENV_VALUE(index)    (*(_environ + (index)))

#define BLOCK_BEGIN(index)  ((byte*)WENV_VALUE(index) - sizeof(int))
#define BLOCK_LENGTH(index) (*(int*)BLOCK_BEGIN(index))
#define BLOCK_END(index)    ((byte*)WENV_VALUE(index) + BLOCK_LENGTH(index))

ELAPI  _EzGetEnvironMutex(IMutex **ppEnvMutex);
extern "C" IMutex *g_pEnvMutex;// = NULL;

EXTERN_C IMutex *EnvironMutex();

void UnicToAnsi(char *pszName, const wchar_t *pwszName);
void AnsiToUnic(wchar_t *pwszName, const char *pszName);

static int AllocEnvBlock(int iEnvStrLen);
static void FreeEnvBlock(int index);
static int ReallocEnvBlock(int index, int iEnvStrLen);

static void *g_pEnvironBase = NULL;
static void *g_pDevpathBase = NULL;
EXTERN_C void * _AllocEnvironBuffer()
{
    g_pEnvironBase = malloc(USER_ENVIRONMENT_SIZE);
    memset(g_pEnvironBase, 0, USER_ENVIRONMENT_SIZE);

    g_pDevpathBase = malloc(USER_DEVPATH_SIZE);
    memset(g_pDevpathBase, 0, USER_DEVPATH_SIZE);

    _wenviron = (wchar_t **)g_pEnvironBase;

    _environ = (char **)(_wenviron + MAX_ENVIRONMENT_ITEM + 1);
    c_pBeginOfBuffer = (byte *)(_environ + MAX_ENVIRONMENT_ITEM + 1);
    c_iMaxBufferSize = (uint_t)_wenviron + USER_ENVIRONMENT_SIZE \
                                                - (uint_t)c_pBeginOfBuffer;
    return g_pEnvironBase;
}

EXTERN_C void _FreeEnvironBuffer()
{
    assert(g_pEnvironBase);
    free(g_pEnvironBase);
    //Aura_free(g_pDevpathBase);
}

EXTERN_C void  _GetEnvBase(
    wchar_t ***pppwszEnvBase, wchar_t **ppwszDevpath)
{
    assert(g_pEnvironBase);
    *pppwszEnvBase = (wchar_t **)g_pEnvironBase;
    *ppwszDevpath = (wchar_t *)g_pDevpathBase;
    return ;
}

EXTERN_C int CDECL _PutEnvironment(const wchar_t *envstring)
{
    wchar_t *equal;
    wchar_t **ppwszTemp =_wenviron;
    int     iKeyLen, iStringLen;
    int     index = -1;
    Boolean    bNeedRemove;
//DebugBreak();
    //
    // Find the '=' and verify it is not the first character in string.
    //
    if ((NULL == envstring) || (NULL == (equal = wcschr(envstring, L'=')))
        || envstring == equal) {
      //  errno = EINVAL;
        return -1;
    }

    iKeyLen = equal - envstring;
    iStringLen = iKeyLen + wcslen(equal);

    // See if need to remove?
    //
    if (L'\0' == *(equal + 1)) {
        bNeedRemove = 1;
    }
    else {
        bNeedRemove = 0;
    }

    WaitResult wr;
    EnvironMutex()->Lock(&wr);

    // See if the string is already in the environment
    //
    for (ppwszTemp = _wenviron; NULL != *ppwszTemp; ppwszTemp++) {
        if (wcsnicmp(envstring, *ppwszTemp, iKeyLen) == 0) {
            if ((*ppwszTemp)[iKeyLen] == L'=') {
                // String is already in the environment
                //
                index = ppwszTemp - _wenviron;
                break;
            }
        }
    }

    if (-1 == index) {
        //
        // String is NOT in the environment.
        //

        // If need to remove, just return succeeded.
        //
        if (bNeedRemove) {
            EnvironMutex()->Unlock();
            return 0;
        }

        // Alloc one block for new string.
        index = AllocEnvBlock(iStringLen);
    }
    else {
        //
        // String is already in the environment.
        //

        // See if need to remove?
        //
        if (bNeedRemove) {
            FreeEnvBlock(index);
            EnvironMutex()->Unlock();
            return 0;
        }
        else {
            index = ReallocEnvBlock(index, iStringLen);
        }
    }

    // Alloc block failed.
    //
    if (-1 == index) {
      //  errno = ENOMEM;
        EnvironMutex()->Unlock();
        return -1;
    }

    // Copy to new block.
    //
    wcscpy(WENV_VALUE(index), envstring);
    UnicToAnsi(ENV_VALUE(index), envstring);

    EnvironMutex()->Unlock();
    return 0;
}

EXTERN_C wchar_t *CDECL _GetEnvironment(const wchar_t *envstring)
{
    size_t len;
    wchar_t **ppwszTemp;
    wchar_t *retval;

    if (NULL == envstring) {
        return NULL;
    }

    ppwszTemp = _wenviron;
    len = wcslen(envstring);

    WaitResult wr;
    EnvironMutex()->Lock(&wr);

    while (*ppwszTemp) {
        if (wcslen(*ppwszTemp) > len && (*(*ppwszTemp + len) == L'=')
            && (0 == wcsnicmp((const wchar_t *)*ppwszTemp, envstring, len))) {
            retval = *ppwszTemp + len + 1;
            EnvironMutex()->Unlock();
            return retval;
        }

        ppwszTemp++;
    }

    EnvironMutex()->Unlock();
    return NULL;
}

static int AllocEnvBlock(int iEnvStrLen)
{
    int index;
    byte *pBeginOfNewBlock;
    int iBlockSize;

    iBlockSize = ROUND4(iEnvStrLen + 1) * (sizeof(wchar_t) + sizeof(char))
                     + sizeof(int);

    if ((NULL == WENV_VALUE(0)) && (iBlockSize > c_iMaxBufferSize)
        || (MAX_ENVIRONMENT_ITEM == s_iEnvCount)) {
        // no space for new block
        return -1;
    }

    pBeginOfNewBlock = (byte*)c_pBeginOfBuffer;

    // Alloc space for a new block
    for (index = 0; MAX_ENVIRONMENT_ITEM > index; index++) {
        if (WENV_VALUE(index)) {
            if (iBlockSize <= (BLOCK_BEGIN(index) - pBeginOfNewBlock)) {
                // Found it!
                break;
            }
        }
        else {
            if (iBlockSize > ((byte*)c_pBeginOfBuffer + c_iMaxBufferSize
                - pBeginOfNewBlock)) {
                // no space for new block
                return -1;
            }
            // Found it!
            break;
        }
        pBeginOfNewBlock = BLOCK_END(index);
    }

    //
    // Now alloc OK! Variable "index" is the index of new block.
    // Increase environment counter.
    //
    s_iEnvCount++;

    // Move down items after index
    //
    char    *pszToBeMoveDown = ENV_VALUE(index);
    char    *pszTemp;
    wchar_t *pwszToBeMoveDown = WENV_VALUE(index);
    wchar_t *pwszTemp;

    int i = index + 1;

    while (pwszToBeMoveDown) {
        assert(pszToBeMoveDown);
        pszTemp             = ENV_VALUE(i);
        pwszTemp            = WENV_VALUE(i);
        ENV_VALUE(i)        = pszToBeMoveDown;
        WENV_VALUE(i)       = pwszToBeMoveDown;
        pszToBeMoveDown     = pszTemp;
        pwszToBeMoveDown    = pwszTemp;
        i++;
    }

    WENV_VALUE(index)   = (wchar_t*)(pBeginOfNewBlock + sizeof(int));
    ENV_VALUE(index)    = (char*)(pBeginOfNewBlock + sizeof(int) \
                          + ROUND4(iEnvStrLen + 1) * sizeof(wchar_t));
    BLOCK_LENGTH(index) = iBlockSize;

    return index;
}

static void FreeEnvBlock(int index)
{
    assert((0 <= index) && (MAX_ENVIRONMENT_ITEM > index));

    // Just move up items after index
    //
    do {
        ENV_VALUE(index)  = ENV_VALUE(index + 1);
        WENV_VALUE(index) = WENV_VALUE(index + 1);
        index++;
    } while (WENV_VALUE(index - 1));

    s_iEnvCount--;
}

static int ReallocEnvBlock(int index, int iEnvStrLen)
{
    int retval;

    int iBlockSize = ROUND4(iEnvStrLen + 1) * (sizeof(wchar_t) + sizeof(char))
                     + sizeof(int);

    // See if old item has enough space.
    //
    if (WENV_VALUE(index + 1)
        ? iBlockSize <= (BLOCK_BEGIN(index + 1) - BLOCK_BEGIN(index))
        : iBlockSize <= ((byte*)c_pBeginOfBuffer + c_iMaxBufferSize
        - BLOCK_BEGIN(index))) {
        WENV_VALUE(index)   = (wchar_t*)(BLOCK_BEGIN(index) + sizeof(int));
        ENV_VALUE(index)    = (char*)(BLOCK_BEGIN(index) + sizeof(int) \
                              + ROUND4(iEnvStrLen + 1) * sizeof(wchar_t));
        BLOCK_LENGTH(index) = iBlockSize;
        return index;
    }

    retval = AllocEnvBlock(iEnvStrLen);
    if (-1 == retval) {
        return -1; // TODO:
    }

    if (retval <= index) {
        index++;
    }
    else {
        retval--;
    }
    FreeEnvBlock(index);
    return retval;
}

extern "C" int __cdecl wcsnicmp(const wchar_t* str1, const wchar_t* str2, size_t count)
{
    int c1, c2;
    if (count) {
        do {
            c1 = *str1++;
            if (c1 >= 'A' && c1 <= 'Z') // convert to lower case.
                c1 -= 'A' - 'a';

            c2 = *str2++;
            if (c2 >= 'A' && c2 <= 'Z') // convert to lower case.
                c2 -= 'A' - 'a';
        } while (--count && c1 && (c1 == c2));
        return (c1 - c2);
    }
    return 0;

}

